package com.xyoto.takeawayUser.service.impl;

import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xyoto.takeawayUser.constant.CacheInstruction;
import com.xyoto.takeawayUser.constant.DataBasesOperationResult;
import com.xyoto.takeawayUser.constant.WechatConstant;
import com.xyoto.takeawayUser.dto.WxLoginDTO;
import com.xyoto.takeawayUser.entity.User;
import com.xyoto.takeawayUser.entity.UserCache;
import com.xyoto.takeawayUser.entity.WxInfo;
import com.xyoto.takeawayUser.enums.UserEnum;
import com.xyoto.takeawayUser.enums.WechatLoginEnum;
import com.xyoto.takeawayUser.exception.ExceptionUtils;
import com.xyoto.takeawayUser.exception.XYOtoException;
import com.xyoto.takeawayUser.mapper.UserMapper;
import com.xyoto.takeawayUser.service.WechatAuthService;
import com.xyoto.takeawayUser.utils.JwtUtils;
import com.xyoto.takeawayUser.utils.RedisCommonUtils;
import com.xyoto.takeawayUser.utils.RedisUtils;
import com.xyoto.takeawayUser.utils.Result;
import com.xyoto.takeawayUser.vo.AuthMessageVO;
import jnr.ffi.annotations.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.concurrent.TimeUnit;

/**
 * 微信授权登录实现
 *
 * @author Administrator
 */
@Service
@Slf4j
public class WechatAuthServiceImpl implements WechatAuthService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisCommonUtils redisCommonUtils;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private HttpServletRequest request;

    /**
     * 开发者授权登录
     *
     * @return
     */
    @Override
    public Result devAuthLogin() {

        Integer integer = userMapper.userExistStatus("String");
        UserCache userCache = new UserCache();
        if (integer < DataBasesOperationResult.SUCCESS) {
            User user = new User();
            user.setOpenid("String");
            user.setNickname("String");
            user.setPath("String");
            user.setPhone("123456789101");

            BeanUtils.copyProperties(user, userCache);

            Integer integer1 = userMapper.saveUser(user);
            if (integer1 == DataBasesOperationResult.ERROR) {
                log.info("模拟用户数据插入失败");
                ExceptionUtils.error(UserEnum.SAVE_USER_ERROR);
            } else {
                log.info("模拟用户数据插入成功");
                userCache.setId(user.getId());
            }
        }
        log.info("查询开发者信息");
        userCache = userMapper.getUserMessageByOpenid("String");

        //生成Token
        String token = JwtUtils.getJwtToken(userCache.getId(), userCache.getNickname());
        redisCommonUtils.set(token, userCache.getId(), 30L, TimeUnit.DAYS);
        redisCommonUtils.hset(CacheInstruction.USER_CACHE, userCache.getId(), userCache);

        AuthMessageVO authMessageVO = new AuthMessageVO();
        authMessageVO.setToken(token);
        authMessageVO.setUserId(userCache.getId());

        return Result.success(authMessageVO);
    }

    /**
     * 微信授权登录
     *
     * @param wxLoginDTO 微信登录实体
     * @return
     */
    @Override
    public Result login(WxLoginDTO wxLoginDTO) {

        WxInfo wxInfo = getSession(wxLoginDTO.getCode());
        //获取手机号
        String phone = getUserPhone(wxLoginDTO, wxInfo.getSession_key());
        Integer integer = userMapper.userExistStatus(wxInfo.getOpenid());
        User user = new User();
        if (integer < DataBasesOperationResult.SUCCESS) {
            user.setOpenid(wxInfo.getOpenid());
            user.setNickname(wxLoginDTO.getNickname());
            user.setPath(wxLoginDTO.getPath());
            user.setPhone(phone);
            log.info("存储用户信息");
            Integer integer1 = userMapper.saveUser(user);
            if (integer1 == DataBasesOperationResult.ERROR) {
                log.info("存储用户信息失败");
                ExceptionUtils.error(UserEnum.SAVE_USER_ERROR);
            }
        }
        log.info("用户创建成功");
        UserCache userCache = userMapper.getUserMessageByOpenid(wxInfo.getOpenid());

        //生成Token
        String token = JwtUtils.getJwtToken(userCache.getId(), userCache.getNickname());
        redisCommonUtils.set(token, userCache.getId(), 30L, TimeUnit.DAYS);
        redisCommonUtils.hset(CacheInstruction.USER_CACHE, userCache.getId(), userCache);

        AuthMessageVO authMessageVO = new AuthMessageVO();
        authMessageVO.setToken(token);
        authMessageVO.setUserId(userCache.getId());

        return Result.success(authMessageVO);
    }

    /**
     * 校验token
     *
     * @return
     */
    @Override
    public Result checkToken() {
        //检查Token的有效性
        boolean flag = redisUtils.checkTokenStatus(request);
        if (flag) {
            try {
                UserCache userCache = redisUtils.getRedisUserMessage(request);
                String token = redisUtils.getUserToken(request);
                //Token有效，更新token的日期
                log.info("更新redis中token的有效时间");
                redisCommonUtils.set(token, userCache.getId(), 30L, TimeUnit.DAYS);
                //将用户信息添加到用户缓存组中
                redisCommonUtils.hset(CacheInstruction.USER_CACHE, userCache.getId(), userCache);
                log.info("更新redis中token的有效时间成功");
            } catch (Exception e) {
                log.info("大概率是【序列化的对象无法匹配】，很小可能是redis挂掉");
                ExceptionUtils.error(WechatLoginEnum.INVALID_TOKEN);
            }
            return Result.success(WechatLoginEnum.TOKEN_EXPIRE_TIME_ALREADY_UPDATE);
        }
        ExceptionUtils.error(WechatLoginEnum.INVALID_TOKEN);
        return Result.success();
    }

    /**
     * 退出登录
     *
     * @return
     */
    @Override
    public Result logout() {

        //获取用户token
        String userToken = redisUtils.getUserToken(request);
        try {
            Integer userId = (Integer) redisCommonUtils.get(userToken);
            if (userId == null){
                ExceptionUtils.error(WechatLoginEnum.USER_NOT_LOGIN);
            }
            redisCommonUtils.del(userToken);
        }catch (Exception e){
            ExceptionUtils.error(WechatLoginEnum.USER_NOT_LOGIN);
        }
        return Result.success();
    }

    /**
     * 调用微信接口获取session_key, openid ，进行登录凭证校验
     *
     * @param code
     * @return
     */
    public WxInfo getSession(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + WechatConstant.APP_ID + "&secret=" + WechatConstant.SECRET + "&js_code=" + code + "&grant_type=authorization_code";
        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
        String body = forEntity.getBody();
        if (body == null) {
            //请求接口出现错误
            log.info("请求微信认证信息接口错误,很可能是url路径错误");
        }
        ObjectMapper objectMapper = new ObjectMapper();
        WxInfo wxInfo = null;
        try {
            //将body中的数据（json格式）转为java实体
            wxInfo = objectMapper.readValue(body, WxInfo.class);
            //如果没有请求成功走这里，请求成功不走
            if (wxInfo.getOpenid() == null) {
                //code过期的情况
                if (wxInfo.getErrcode() == WechatConstant.EXPIRE_RESULT_STATUS) {
                    ExceptionUtils.error(WechatLoginEnum.CODE_ALREADY_EXPIRE);
                }
                //code重复使用的情况
            }
        } catch (JsonProcessingException e) {
            log.info("body数据转换java实体错误");
        }
        return wxInfo;
    }

    /**
     * 获取用户的手机号
     *
     * @param wxLogin    微信登录参数
     * @param sessionKey 用户密钥
     * @return
     */
    public String getUserPhone(WxLoginDTO wxLogin, String sessionKey) {
        return decryptData(wxLogin.getEncryptedData(), sessionKey, wxLogin.getIv());
    }

    /**
     * 获取加密的手机号信息
     *
     * @param encryptedData 加密的手机号信息
     * @param sessionKey    用户密钥
     * @param iv            加密密钥
     * @return 手机号
     */
    public static String decryptData(String encryptedData, String sessionKey, String iv) {
        byte[] encData = Base64.decode(encryptedData);
        byte[] ivs = Base64.decode(iv);
        byte[] key = Base64.decode(sessionKey);
        String phone = null;
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivs);
        //调用AES/DES加密算法包最精要的就是下面两句话对称加密
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            JSONObject jsonObject = JSON.parseObject(new String(cipher.doFinal(encData), StandardCharsets.UTF_8));
            phone = jsonObject.getString("purePhoneNumber");
        } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
            log.info("获取加密手机号信息失败");
            ExceptionUtils.error(WechatLoginEnum.GET_SECRET_FAILED);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            log.info("手机信息类型转换错误");
            ExceptionUtils.error(WechatLoginEnum.PHONE_TYPE_TRANSFORM_FAILED);
        }
        if (phone == null) {
            log.info("获取加密手机号信息失败");
            ExceptionUtils.error(WechatLoginEnum.GET_SECRET_FAILED);
        }
        return phone;
    }

}
